/*
 * User: ophir
 * Date: Sep 8, 2008
 * Time: 11:40:58 PM
 */
package org.conann.components;

import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.Sets;
import org.conann.Stereotype;
import org.conann.configuration.Configuration;
import org.conann.container.Creator;
import org.conann.metadata.BeanMD;
import org.conann.metadata.validation.WebBeansValidationException;
import org.conann.util.AnnotationUtil;
import static org.conann.util.DevelopmentUtil.unsupportedOperation;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import static java.util.Collections.unmodifiableSet;
import java.util.Map;
import java.util.Set;

public abstract class AbstractBean<T> implements BeanImpl<T> {

    private final Class<T> type;
    private final BeanMD beanMD;
    private final Set<Annotation> bindingTypes;
    private final Class<? extends Annotation> scopeType;
    private final Class<? extends Annotation> deploymentType;
    private final String name;
    private final Set<Class<? super T>> apiTypes;

    @SuppressWarnings({"OverridableMethodCallDuringObjectConstruction", "AbstractMethodCallInConstructor"})
    protected AbstractBean(Configuration configuration, Class<T> type) {
        checkNotNull(type);
        checkNotNull(configuration);
        this.type = type;
        beanMD = new BeanMD(type);
        bindingTypes = resolveBinding();
        scopeType = resolveScope();
        deploymentType = resolveDeployment(configuration);
        name = resolveName();
        apiTypes = resolveApiTypes();
    }

    protected Set<Annotation> resolveBinding() {
        return beanMD.getBindingTypes();
    }

    protected Class<? extends Annotation> resolveDeployment(Configuration configuration) {
        if (beanMD.getDeploymentType() != null) {
            return beanMD.getDeploymentType();
        }
        Class<? extends Annotation> deploymentInAllStereotypes = null;
        for (Stereotype stereotype : beanMD.getStereotypes()) {
            Class<? extends Annotation> stereotypeDeploymentType = stereotype.getDeploymentType();
            if (deploymentInAllStereotypes == null) {
                deploymentInAllStereotypes = stereotypeDeploymentType;
            } else {
                if (!deploymentInAllStereotypes.equals(stereotypeDeploymentType)) {
                    throw new WebBeansValidationException("invalid component definition [%s]:\nNot all stereotypes declare the same deployment type.", type.getName());
                }
                deploymentInAllStereotypes = stereotypeDeploymentType;
            }
        }
        return deploymentInAllStereotypes;
    }

    protected abstract String resolveName();

    protected abstract String getDefaultName();

    public Creator<T> getCreator() {
        throw unsupportedOperation();
    }

    public Set<Class<? super T>> getTypes() {
        return apiTypes;
    }

    public Class<T> getImplClass() {
        return type;
    }

    public Class<? extends Annotation> getScopeType() {
        return scopeType;
    }

    public Class<? extends Annotation> getDeploymentType() {
        return deploymentType;
    }

    public Set<Annotation> getBindingTypes() {
        return bindingTypes;
    }

    protected BeanMD getComponentMD() {
        return beanMD;
    }

    public String getName() {
        return name;
    }

    public boolean matches(Annotation annotation) {
        checkNotNull(annotation);
        Annotation matchingAnnotation = getComponentMD().getByClass(annotation.annotationType());
        if (matchingAnnotation == null) {
            return false;
        }
        Map<String, Object> matchingAnnotationParams = getComponentMD().getParams(matchingAnnotation);
        Map<String, Object> annotationParams = AnnotationUtil.getParameters(annotation);
        for (Map.Entry<String, Object> param : annotationParams.entrySet()) {
            if (!matchingAnnotationParams.get(param.getKey()).equals(param.getValue())) {
                return false;
            }
        }
        return true;
    }

    @SuppressWarnings({"unchecked"})
    private Set<Class<? super T>> resolveApiTypes() {
        Set<Class<?>> apiTypes = Sets.newHashSetWithExpectedSize(8);
        Class<? super T> typeIterator = type;
        do {
            apiTypes.add(typeIterator);
            apiTypes.addAll(Arrays.asList(typeIterator.getInterfaces()));
            typeIterator = typeIterator.getSuperclass();
        } while (!Object.class.equals(typeIterator));
        // generics drove me mad:
        Set<Class<? super T>> result = Sets.newHashSetWithExpectedSize(apiTypes.size());
        for (Class<?> apiType : apiTypes) {
            // godamn non-covariant generics!
            result.add((Class<? super T>) apiType);
        }
        return unmodifiableSet(result);
    }

    protected abstract Class<? extends Annotation> resolveScope();
}